﻿<!doctype html>
<html>
  <head>
    <meta http-equiv="content-type" content="text/html; charset=UTF-8">
    <title>Processing.js Performance Tests</title>
    <script type="text/javascript" src="https://www.google.com/jsapi"></script>
    <script>
      google.load('visualization', '1', {'packages':['corechart']});
    </script>
    <script src="../../processing.js"></script>
    <script src="tests.js"></script>
    <style>
      .test  { border: solid 1px; margin: 5px; }
      .title { margin-left: 5px; }
    </style>
  </head>
  <body>
    <h1>Processing.js Performance Test Runner</h1>
    <p>To start the tests click Start.  Each test is run in order (downloaded from the server).  Tests are run for a maximum number of frames (specified below), and the time recorded.</p>

    <p>Reference tests can be run from <a href="/ref" target="_blank">here.</a>

    <h3>Settings</h3>
    <div>Run Tests: <select id="test-type" onchange="updateSelectedTests();">
      <option value="2D,3D" selected="selected">2D and 3D</option>
      <option value="2D">2D Only</option>
      <option value="3D">3D Only</option>
      <option value="Blend">Blend Only</option>
      <option value="PImage">PImage Only</option>
      <option value="PVector">PVector Only</option>
      <option value="Crisp">Crisp</option>
      <option value="Calibration">Calibration</option>
    </select>&nbsp;
    Iterations Per Test: <input type="text" size="4" id="iterations" onchange="updateIterations();">&nbsp;
    <input onclick="runTests(selectedTests);" type="button" value="Start"></input><span id="testCount"></span></div>

    <h3>Results [total time for <span id="curTest">0</span> tests: <span id="totalTime">0</span>ms] <button id="report" style="display:none" onclick="report();">Generate Report</button></h3>
    <div id="status" style="margin-bottom: 10px; display: none;">Running... (this may take some time)</div>
    <div id="results-chart"></div>
    <div id="results" style="margin: 5px; padding-top: 10px;"></div>
    <div id="total"></div>

    <script>
      var MAX_TEST_TIME = 5000; // ms of time to allow a test to run before halting and failing.
      var selectedTests = [];
      var finishedTests;
      var failedTests = 0;
      var totalTime = 0;
      var total = document.getElementById('total');
      total.innerHTML = '';

      updateSelectedTests();

      // Tolerance values and kernel for blur
      var iterationCount = 1000;
      document.getElementById('iterations').value = iterationCount;

      function generateReport(tests) {
        var report = {
                       testDate: Date.now(),
                       userAgent: window.navigator.userAgent,
                       data: []
                     };

        for (var i=0; i < tests.length; i++) {
          if (tests[i].completed) {
            report.data.push(tests[i]);
          }
        }

        return JSON.stringify(report);
      }

      function report() {
        if (!finishedTests) {
          alert("You must run the tests first.");
          return;
        }

        // Using text/plain vs. application/json so it's easier to copy/paste
        document.location.href = "data:text/plain," + generateReport(finishedTests);
      }

      /**
       * Set up the test run filters
       */
      function updateSelectedTests() {
        var selectControl = document.getElementById('test-type');
        var selectedTags = selectControl.value.split(',');

        selectedTests = new Array();
        for (var i = 0; i < tests.length; ++i) {
          var found = false;
          // checking if any selected tags present in test tags
          for (var j = 0; j < selectedTags.length && !found; ++j) {
            for (var q = 0; q < tests[i].tags.length && !found; ++q) {
              if (tests[i].tags[q] == selectedTags[j]) found = true;
            }
          }
          if (found) {
            selectedTests.push(tests[i]);
          }
        }

        var testCount = document.getElementById('testCount');
        testCount.innerHTML = '&nbsp;(' + selectedTests.length + ' tests)';
      }


      /**
       * Change how many times each perf test runs before reporting
       */
      function updateIterations() {
        var newIterationCount = document.getElementById('iterations').value;
        if (newIterationCount) {
          iterationCount = parseInt(newIterationCount, 10);
        }
      }

      /**
       * get a file with cache breaking t=...
       */
      function download(file) {
        var req = new XMLHttpRequest();
        req.open('GET', file + "?t=" + Date.now(), false);
        if (req.overrideMimeType) {
          req.overrideMimeType('text/plain');
        }
        req.send(null);
        if (req.status != 200 && req.status !=0) {
          return null;
        } else {
          return req.responseText;
        }
      }

      /**
       * get a test's source code
       */
      function getTest(testName) {
        var responseText = download(testName);

        function is3D(script) {
          // look for size(100,100,OPENGL) or size(100,100,P3D)
          var match = script.match(/size\([\s]*[\d]+[\s]*\,[\s]*[\d]+[\s]*\,?([^\)]+)?\)/);
          if (match && match[1]) {
            return match[1] == "OPENGL" || match[1] == "P3D";
          }
          return false;
        }

        if (!responseText) {
          return null;
        }

        // Split out the canvas info in the comment:
        var test = {name: testName, code: responseText};
        test.is3D = is3D(test.code);
        return test;
      }

      /**
       * Generate the performance chart after running all tests
       */
      function drawResultsChart() {
        var dataTable = new google.visualization.DataTable();
        dataTable.addColumn('string', 'Test');
        dataTable.addColumn('number', 'Time per Frame (ms)');
        var test, i;
        for (i=0; i<tests.length; i++) {
          test = tests[i];
          dataTable.addRow([test.path,
                            test.time / (test.iterationOverride || iterationCount)]);
        }

        var chart = new google.visualization.BarChart(document.getElementById('results-chart'));
        chart.draw(dataTable,
                   { title: 'Performance Test Results',
                     width: 800,
                     height: 600,
                     legend: 'none',
                     vAxis: {title: 'Test', textPosition: 'none'},
                     hAxis: {title: 'Time per Frame (ms)'}
                   });
      }

      /**
       * Run all performance tests
       */
      function runTests(tests) {
        var results = document.getElementById('results');
        results.innerHTML = '';
        var total = document.getElementById('total');
        total.innerHTML = '';
        var chart = document.getElementById('results-chart');
        chart.innerHTML = '';

        document.getElementById('report').style.display = "none";
        finishedTests = null;
        failedTests = 0;

        totalTime = 0;

        var buildCanvas = function(id, w, h) {
          var c = document.createElement('canvas');
          c.id = id;
          c.width = w;
          c.height = h;
          c.className = "test";
          return c;
        };

        var link = function(name) {
          return '<a href="' + name + '">' + name + '</a>';
        };

        var titleText = function(testNumber, testTotal, time, testName, testIterations, failMessage) {
          return "Test (" + testNumber + "/" + testTotal + ") " + link(testName) +
                 (failMessage ? " -- FAILED (" + failMessage + ")" : " -- Total Time for " + testIterations + " frames: <b>" + time + "ms</b>");
        };

        var tl = tests.length;
        var lastTest = 0;
        function nextTest(testNum) {

          if (testNum < tl) {
            window.setTimeout(function() { runOne(testNum); }, 50);
          } else {
            var passed = tl - failedTests;
            var info = "Completed " + passed + " of " + tl + " tests (" + failedTests + " failed).";
            document.getElementById('status').innerHTML = info;
            total.innerHTML = info;
            finishedTests = tests;

            document.getElementById('status').style.display = "none";
            document.getElementById('report').style.display = "inline";

            // sort the tests into failed and passed
            var sortedDivs = [];
            while (results.hasChildNodes()) {
              sortedDivs.push(results.firstChild);
              results.removeChild(results.firstChild);
            }

            var failedDiv = document.createElement('div');
            failedDiv.innerHTML = '<hr><h3>Failed ' + failedTests + ' of ' + (passed + failedTests) + '</h3>';
            results.appendChild(failedDiv);

            var passDiv = document.createElement('div');
            passDiv.innerHTML = '<hr><h3>Passed ' + passed + ' of ' + (passed + failedTests) + '</h3>';
            results.appendChild(passDiv);

            for (var i = 0; i < sortedDivs.length; i++) {
              if (sortedDivs[i].className === 'failed') {
                failedDiv.appendChild(sortedDivs[i]);
              } else {
                passDiv.appendChild(sortedDivs[i]);
              }
            }

            if (failedDiv.childNodes.length === 0) {
              var temp = document.createElement('p');
              temp.innerHTML = 'No failed tests';
              failedDiv.appendChild(temp);
            }

            drawResultsChart();
          }
        }

        function runOne(i) {
          if (!tests[i]) {
            alert("Error: no test found.");
            return;
          }

          var test = getTest(tests[i].path);
          var iterCount = tests[i].iterationOverride || iterationCount;
          var result = document.createElement('div');
          result.id = test.name;
          var title = document.createElement('div');
          title.className = "title";
          result.appendChild(title);
          results.appendChild(result);

          if (test) {
            var canvas = buildCanvas(test.name + '-canvas', test.width, test.height);
            result.appendChild(canvas);

            // draw the current version from code, timing it
            var p;
            try {

              // Force the test to not loop--we'll do that.
              var code = "noLoop();\n" + test.code;
              p = new Processing(canvas, code);

              function runBenchmark() {
                try {
                  var startTime = Date.now(),
                      failed = false;

                  for (var iters = 0; iters < iterCount; iters++) {
                    if ( Date.now() - startTime > MAX_TEST_TIME ) {
                      // Exceeded allowable test run time, fail and bail
                      failed = true;
                      failedTests++;
                      break;
                    }
                    p.redraw();
                  }

                  var endTime = Date.now();
                  var diff = endTime - startTime;
                  totalTime += diff;

                  if (failed) {
                    result.className = "failed";
                    title.innerHTML = titleText(i+1, tl, "-", test.name, iterCount,
                                      "Test exceeded maximum test time (" + MAX_TEST_TIME + "ms), halted.");
                  } else {
                    result.className = "passed";
                    title.innerHTML = titleText(i+1, tl, diff, test.name, iterCount);
                  }
                  document.getElementById('totalTime').innerHTML = totalTime;
                  document.getElementById('curTest').innerHTML = (i+1);

                  // Mark this test run a success with time
                  tests[i].completed = true;
                  tests[i].time = diff;
                } catch (e) {
                  result.className = "failed";
                  title.innerHTML = titleText(i+1, tl, 0, test.name, iterCount, "Processing failed during execution: " + e.toString());
                  nextTest(i+1);
                }
                var is3D = p.use3DContext;
              }
              nextTest(i+1);
              if (!p.draw) {
                // preload present
                var initInterval, attempts = 20;
                initInterval = setInterval(function() {
                  if (p.draw) {
                    clearInterval(initInterval);
                    runBenchmark();
                    return;
                  }
                  if (--attempts <= 0) {
                    clearInterval(initInterval);
                    result.className = "failed";
                    title.innerHTML = titleText(i+1, tl, 0, test.name, iterCount, "Processing failed initialization");
                    nextTest(i+1);
                    return;
                  }
                }, 100);
                return;
              }
              runBenchmark();
              p.exit();
              p = null;
            } catch (e) {
              result.className = "failed";
              title.innerHTML = titleText(i+1, tl, 0, test.name, iterCount, "Processing failed: " + e.toString());
              nextTest(i+1);
            }
          }
        };
        window.setTimeout(function() { runOne(0); }, 25);
      };
    </script>

    <!-- autorun -->
    <script>
      document.addEventListener("DOMContentLoaded", function() {
        var loc = window.location.toString();
        if(loc.indexOf("autorun=true") !== -1) {
          runTests(selectedTests);
        }
      }, false);
    </script>
  </body>
</html>
